#include "napi/native_api.h"
#include <string>
#include "hilog/log.h"
#include "cJSON.h"

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x00001  // 全局domain宏，标识业务领域
#define LOG_TAG "cJSON_Test"   // 全局tag宏，标识模块日志tag

static napi_value cJSONVersion(napi_env env, napi_callback_info info)
{
    napi_value ver;
    const char* str = cJSON_Version();
    if (napi_create_string_utf8(env, str, strlen(str), &ver) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create string object failed");
        return nullptr;
    }
    return ver;
}

static void freeCJSONObject(napi_env env, void *nativeObject, void *finalize)
{
    cJSON* cObject = reinterpret_cast<cJSON*>(nativeObject);
    cJSON_Delete(cObject);
}

static napi_value cJSONCreateObject(napi_env env, napi_callback_info info)
{
    cJSON* cObject = cJSON_CreateObject();
    if (cObject == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "create c cJSON object failed");
        return nullptr;
    }

    napi_value jsObject;
    if (napi_create_object(env, &jsObject) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create js cJSON object failed");
        cJSON_Delete(cObject);
        return nullptr;
    }
    if (napi_wrap(env, jsObject, reinterpret_cast<void *>(cObject), freeCJSONObject, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "wrap js and c object failed");
        cJSON_Delete(cObject);
        return nullptr;
    }

    return jsObject;
}

static napi_value cJSONAddItemToObject(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* object = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&object)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }
    
    size_t len;
    if (napi_get_value_string_utf8(env, args[1], NULL, 0, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    std::shared_ptr<char> key(new char[len + 1], [](char* p){ delete []p; });
    if (key == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Memory allocation failed");
        return nullptr;
    }
    key.get()[len] = '\0';
    if (napi_get_value_string_utf8(env, args[1], key.get(), len + 1, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    
    cJSON* item = nullptr;
    if (napi_unwrap(env, args[2], reinterpret_cast<void **>(&item)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap item failed");
        return nullptr;
    }

    napi_value succ = nullptr;
    if (napi_get_boolean(env, !!cJSON_AddItemToObject(object, key.get(), item), &succ)) {
        OH_LOG_ERROR(LOG_APP,  "create boolean object failed");
        return nullptr;
    }
    return succ;
}

static napi_value cJSONAddStringToObject(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* object = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&object)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }
    
    size_t len;
    if (napi_get_value_string_utf8(env, args[1], NULL, 0, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    std::shared_ptr<char> name(new char[len + 1], [](char* p){ delete []p; });
    if (name == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Memory allocation failed");
        return nullptr;
    }
    name.get()[len] = '\0';
    if (napi_get_value_string_utf8(env, args[1], name.get(), len + 1, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    
    if (napi_get_value_string_utf8(env, args[2], NULL, 0, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    std::shared_ptr<char> value(new char[len + 1], [](char* p){ delete []p; });
    if (value == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Memory allocation failed");
        return nullptr;
    }
    value.get()[len] = '\0';
    if (napi_get_value_string_utf8(env, args[2], value.get(), len + 1, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    
    if (cJSON_AddStringToObject(object, name.get(), value.get()) == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "AddStringToObject failed");
        return nullptr;
    }
    return args[0];
}

static napi_value cJSONAddNumberToObject(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* object = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&object)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }
    
    size_t len;
    if (napi_get_value_string_utf8(env, args[1], NULL, 0, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    std::shared_ptr<char> name(new char[len + 1], [](char* p){ delete []p; });
    if (name == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Memory allocation failed");
        return nullptr;
    }
    name.get()[len] = '\0';
    if (napi_get_value_string_utf8(env, args[1], name.get(), len + 1, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    
    double value = 0.0;
    if (napi_get_value_double(env, args[2], &value) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    
    if (cJSON_AddNumberToObject(object, name.get(), value) == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "AddNumberToObject failed");
        return nullptr;
    }
    return args[0];
}

static napi_value cJSONPrint(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* object = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&object)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }

    char* out = cJSON_Print(object);
    if (out == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Print failed");
        return nullptr;
    }
    
    napi_value value;
    if (napi_create_string_utf8(env, out, strlen(out), &value) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create string utf8 failed");
        free(out);
        return nullptr;
    }
    free(out);
    return value;
}

static napi_value cJSONAddFalseToObject(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* object = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&object)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }
    
    size_t len;
    if (napi_get_value_string_utf8(env, args[1], NULL, 0, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    std::shared_ptr<char> name(new char[len + 1], [](char* p){ delete []p; });
    if (name == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Memory allocation failed");
        return nullptr;
    }
    name.get()[len] = '\0';
    if (napi_get_value_string_utf8(env, args[1], name.get(), len + 1, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }

    if (cJSON_AddFalseToObject(object, name.get()) == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "AddFalseToObject failed");
        return nullptr;
    }
    return args[0];
}

static napi_value cJSONPrintPreallocated(napi_env env, napi_callback_info info)
{
    size_t argc = 4;
    napi_value args[4] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* item = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&item)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }
    
    int len;
    if (napi_get_value_int32(env, args[2], &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid length argument");
        return nullptr;
    }

    bool result = false;
    napi_status status = napi_is_arraybuffer(env, args[1], &result);  
    if (status != napi_ok) {
        OH_LOG_ERROR(LOG_APP, "Node-API napi_is_arraybuffer fail");
        return nullptr;
    }
    char* buffer = nullptr;
    size_t bufLen = 0;
    if (napi_get_arraybuffer_info(env, args[1], reinterpret_cast<void **>(&buffer), &bufLen) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Memory allocation failed");
        return nullptr;
    }
    int format = false;
    if (napi_get_value_int32(env, args[3], &format) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid length argument");
        return nullptr;
    }
    
    napi_value value;
    if (napi_create_int32(env, cJSON_PrintPreallocated(item, buffer, bufLen, format), &value) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create int32 failed");
        return nullptr;
    }

    return value;
}

static napi_value cJSONDelete(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* item = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&item)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap item failed");
        return nullptr;
    }
    
    cJSON_Delete(item);
    return nullptr;
}

static napi_value cJSONCreateArray(napi_env env, napi_callback_info info)
{
    cJSON* array = cJSON_CreateArray();
    if (array == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "create c cJSON object failed");
        return nullptr;
    }
    napi_value jsObject;
    if (napi_create_object(env, &jsObject) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create js cJSON object failed");
        cJSON_Delete(array);
        return nullptr;
    }
    if (napi_wrap(env, jsObject, reinterpret_cast<void *>(array), freeCJSONObject, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "wrap js and c object failed");
        cJSON_Delete(array);
        return nullptr;
    }
    return jsObject;
}

static napi_value cJSONAddItemToArray(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    
    cJSON* array = nullptr;
    if (napi_unwrap(env, args[0], reinterpret_cast<void **>(&array)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }
    
    cJSON* item = nullptr;
    if (napi_unwrap(env, args[1], reinterpret_cast<void **>(&item)) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "unwrap object failed");
        return nullptr;
    }

    napi_value succ = nullptr;
    if (napi_get_boolean(env, !!cJSON_AddItemToArray(array, item), &succ)) {
        OH_LOG_ERROR(LOG_APP,  "create int32 failed");
        return nullptr;
    }
    return succ;
}

static napi_value cJSONCreateString(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }

    size_t len;
    if (napi_get_value_string_utf8(env, args[0], NULL, 0, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    std::shared_ptr<char> buffer(new char[len + 1], [](char* p){ delete []p; });
    if (buffer == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Memory allocation failed");
        return nullptr;
    }
    buffer.get()[len] = '\0';
    if (napi_get_value_string_utf8(env, args[0], buffer.get(), len + 1, &len) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Invalid string argument");
        return nullptr;
    }
    
    cJSON* obj = cJSON_CreateString(buffer.get());
    if (obj == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "create c cJSON object failed");
        return nullptr;
    }
    
    napi_value jsObject;
    if (napi_create_object(env, &jsObject) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create js cJSON object failed");
        cJSON_Delete(obj);
        return nullptr;
    }
    if (napi_wrap(env, jsObject, reinterpret_cast<void *>(obj), freeCJSONObject, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "wrap js and c object failed");
        cJSON_Delete(obj);
        return nullptr;
    }
    return jsObject;
}

static napi_value cJSONCreateStringArray(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }

    int length = false;
    if (napi_get_value_int32(env, args[1], &length)) {
        OH_LOG_ERROR(LOG_APP,  "Invalid length argument");
        return nullptr;
    }

    std::shared_ptr<char*> buffer(new char*[length], [](char** p){ delete []p; });
    if (buffer == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "Invalid length argument");
        return nullptr;
    }
    std::vector<std::shared_ptr<char>> tmp;
    for (uint32_t i = 0; i < length; ++i) {
        napi_value element;
        napi_get_element(env, args[0], i, &element); // 获取单个元素
        size_t strLen;
        napi_get_value_string_utf8(env, element, nullptr, 0, &strLen);
        std::shared_ptr<char> item(new char[strLen + 1], [](char* p){ delete []p; });
        tmp.push_back(item);
        buffer.get()[i] = item.get();
        if (buffer.get()[i] == nullptr) {
            continue;
        }
        buffer.get()[i][strLen] = '\0';
        napi_get_value_string_utf8(env, element, buffer.get()[i], strLen + 1, &strLen);
    }

    cJSON* obj = cJSON_CreateStringArray(buffer.get(), length);
    if (obj == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "create c cJSON object failed");
        return nullptr;
    }

    napi_value jsObject;
    if (napi_create_object(env, &jsObject) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create js cJSON object failed");
        cJSON_Delete(obj);
        return nullptr;
    }
    if (napi_wrap(env, jsObject, reinterpret_cast<void *>(obj), freeCJSONObject, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "wrap js and c object failed");
        cJSON_Delete(obj);
        return nullptr;
    }
    return jsObject;
}

static napi_value cJSONCreateIntArray(napi_env env, napi_callback_info info)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "Failed to parse arguments");
        return nullptr;
    }
    int length = 0;
    if (napi_get_value_int32(env, args[1], &length)) {
        OH_LOG_ERROR(LOG_APP,  "Invalid length argument");
        return nullptr;
    }

    std::shared_ptr<int32_t> buffer(new int32_t[length], [](int32_t* p){ delete []p; });
    if(buffer == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "malloc buffer failed");
        return nullptr;
    }
    for (uint32_t i = 0; i < length; ++i) {
        napi_value element;
        napi_get_element(env, args[0], i, &element); // 获取单个元素
        napi_get_value_int32(env, element, &(buffer.get()[i]));
    }

    cJSON* obj = cJSON_CreateIntArray(buffer.get(), length);
    if (obj == nullptr) {
        OH_LOG_ERROR(LOG_APP,  "create c cJSON object failed");
        return nullptr;
    }

    napi_value jsObject;
    if (napi_create_object(env, &jsObject) != napi_ok) {
        OH_LOG_ERROR(LOG_APP,  "create js cJSON object failed");
        cJSON_Delete(obj);
        return nullptr;
    }
     if (napi_wrap(env, jsObject, reinterpret_cast<void *>(obj), freeCJSONObject, nullptr, nullptr) != napi_ok) {
         OH_LOG_ERROR(LOG_APP,  "wrap js and c object failed");
         cJSON_Delete(obj);
         return nullptr;
     }
    return jsObject;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "cJSON_CreateObject", nullptr, cJSONCreateObject, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_AddItemToObject", nullptr, cJSONAddItemToObject, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_AddStringToObject", nullptr, cJSONAddStringToObject, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_AddNumberToObject", nullptr, cJSONAddNumberToObject, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_Print", nullptr, cJSONPrint, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_AddFalseToObject", nullptr, cJSONAddFalseToObject, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_PrintPreallocated", nullptr, cJSONPrintPreallocated, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_Delete", nullptr, cJSONDelete, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_CreateArray", nullptr, cJSONCreateArray, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_AddItemToArray", nullptr, cJSONAddItemToArray, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_CreateString", nullptr, cJSONCreateString, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_CreateStringArray", nullptr, cJSONCreateStringArray, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_CreateIntArray", nullptr, cJSONCreateIntArray, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cJSON_Version", nullptr, cJSONVersion, nullptr, nullptr, nullptr, napi_default, nullptr },
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module JsonModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "cJSON",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&JsonModule);
}
